// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: hyuan

#pragma once

#include <iomanip>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>

#include "coin2/base/time.h"
#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_base/base_auth.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/api_util/json_writer.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

namespace {

typedef boost::tokenizer<boost::char_separator<char> > tokenizer;

std::string ExpandToken(const std::string& token, const std::string& name) {
  std::string res = "";
  if (token.substr(0, name.length()) == name) {
    std::string value_str = token.substr(name.length(), token.length() - name.length());
    boost::algorithm::trim_if(value_str, [](char ch) {
      return (ch == '{') || (ch == '}');
    });
    boost::char_separator<char> value_sep(",");
    tokenizer values(value_str, value_sep);
    BOOST_FOREACH(const std::string& value, values)
    {
      std::string value_copy = boost::algorithm::trim_copy_if(value, [](char ch) {
        return (ch == '"');
      });
      res += name + value_copy + "&";
    }
    if (!res.empty()) {
      res = res.substr(0, res.length() - 1);
    }
  }
  return res;
}

std::string ExpandStringArray(const std::string& query_string) {
  std::string res = "";
  boost::char_separator<char> token_sep("&");
  tokenizer tokens(query_string, token_sep);
  BOOST_FOREACH(const std::string& token, tokens)
  {
    std::string type_str = ExpandToken(token, "type=");
    if (!type_str.empty()) {
      res += type_str + "&";
      continue;
    }
    std::string symbol_str = ExpandToken(token, "symbol=");
    if (!symbol_str.empty()) {
      res += symbol_str + "&";
      continue;
    }
    res += token + "&";
  }
  if (!res.empty()) {
    res = res.substr(0, res.length() - 1);
  }
  return res;
}

}  // namespace

class BtcturkRestAuth : public BaseRestAuth {
 public:
  explicit BtcturkRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {
  }
  explicit BtcturkRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(std::to_string(port)),
          bind_ip_(bind_ip) {
  }

  std::string GetSignature(const std::string& secret_key,
      const std::string& api_key,  const std::string& ts) const {
    if (secret_key.empty() || api_key.empty()) {
      throw std::runtime_error("[Btcturk] API key and secret key are required");
    }
    std::string cred = api_key + ts;
    return ApiSignatureUtil::GetSignatureByHmacSHA256(cred, impl::base64_decode(secret_key));
  }

  std::vector<std::pair<std::string, std::string>> GetHeaderSet() const {
    std::vector<std::pair<std::string, std::string>> out;
    std::string ts_str = std::to_string(GetCurrentTimestamp() / 1000000LL);
    std::string signa = GetSignature(key_.secret_key(), key_.access_key(), ts_str);
    out.push_back(std::make_pair("X-PCK", key_.access_key()));
    out.push_back(std::make_pair("X-Stamp", ts_str));
    out.push_back(std::make_pair("X-Signature", signa));
    return out;
  }

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    query_string = ExpandStringArray(query_string);
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewGet(host_, port_, bind_ip_, target, GetHeaderSet());
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    CHECK(query_string.empty());
    std::string post_body = builder->ComputePostBody();
    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewPost(host_, port_, bind_ip_, target, GetHeaderSet(), post_body);
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewDelete(host_, port_, bind_ip_, target, GetHeaderSet(), "");
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
};
